#!/usr/bin/env python
import os
import re
import mad
import sys
import getopt
import math
import datetime
import subprocess

##############################################################################
# Class Definitions
##############################################################################

class CD:

  """
    Initialize object with constants. This might take a bit as we will need to
    build each CD based on a tracks length. This cannot be more than 80 minues
    long as the max a cd length can be is 80min.
  """
  def __init__(self, root_dir, track_lst, key, length, number):
    self.root_dir = root_dir
    self.cue_path = ""
    self.wav_path = ""
    self.key = key
    self.number = number
    self.length = length
    self.track_lst = track_lst
    self.track_idx = self.build_track_idx()
    self.wav_dir = self.build_wav_dir() 
  def build_m3u(self):
    m3u = "#EXTM3U\n"
    for track in self.track_lst:
      m3u += "#EXTINF:%s,%s_%s - %s_%s\n" %(track.length, track.bpm, \
        track.artist, track.bpm, track.name)
      m3u += "%s\n\n" %track.path
    return m3u

  def build_cue(self):
    counter = 1
    cue  = "TITLE \"%s\"\n" %self.key
    cue += "PERFORMER \"%s\"\n" %self.key
    cue +="FILE \"%s\" WAVE\n" %self.wav_path
    for track in self.track_lst:
      title_string = \
        track.bpm + " " + \
        track.artist + " " + \
        track.name + " " + \
        track.remix
      title_string = title_string[0:12]
      if len("%s" %counter) < 2:
        ctr_str = "0%s" %counter
      else:
        ctr_str = "%s" %counter
      cue +="  TRACK %s AUDIO\n" %ctr_str
      cue +="    TITLE \"%s\"\n" %title_string
      cue +="    PERFORMER \"%s\"\n" %title_string
      cue +="    INDEX 01 %s\n" %self.track_idx[counter-1]
      counter += 1
    return cue

  def add_sec_to_time(timeval, seconds):
    secs = timeval.hour * 3600 + timeval.minute * 60 + timeval.second
    secs += seconds
    return datetime.time(0,secs)

  def build_track_idx(self):
    track_idx = []
    ts = datetime.datetime(2000,1,1,0,0,0)
    for track in self.track_lst:
      tmp = re.split(":",ts.strftime("%H:%M:%S"))
      track_idx.append("%s:%s:00" %((int(tmp[0]) * 60) + int(tmp[1]), tmp[2]))
      ts += datetime.timedelta(0,track.length)
    return track_idx

  def build_wav_dir(self):
    wav_dir = os.path.join(self.root_dir, "CD%s_%s" %(self.number, self.key))
    if not os.path.exists(wav_dir):
      os.mkdir(wav_dir) 
    return wav_dir

class Track:
  """
  Initialize object with constants. This is sloppy and will need to change
  later. We are also quite messy in how we add mad_obj for just the track
  length. Make sure to add cache for objects at a later date as well.

  string >> path       directory listing from ROOT (i.e. /) to physical file
  string >> full_name  in the form: "KEY - Artist - Track Name  Remix"
  """
  def __init__(self, path, filename):
    self.path = path
    self.wav = ""
    self.cue_path = ""
    self.filename = filename
    self.mad_obj = mad.MadFile(path)
    self.length = self.get_track_length()
    # Parse filename to get the rest:
    # I.E. KEY - BPM - Artist-Track Name-Remix
    self.key,\
    self.bpm,\
    self.artist,\
    self.name,\
    self.remix\
    = self.parse_track_name()
  ###
  # Local Functions
  ##
  def debug(self):
    print (self.path, self.key, self.bpm, self.artist, self.name, self.remix)

  def parse_track_name(self):
    dir_lst = re.split("\/", self.path)
    file_name = re.split("\.mp3", dir_lst[len(dir_lst)-1])[0]
    file_name = re.sub("\-", " - ", re.sub("\_", " and ", file_name))
    name_parse = re.split("\s*-\s*", file_name)
    name_parse[1] = "%s" %int(round(float(name_parse[1])))
    # Just in case the formatting isn't correct
    while len(name_parse) < 5:
      name_parse.append("")
    return (name_parse)

  def get_track_length(self):
    """
    Converts to python time object
    << time object
    """
    return int(math.ceil(self.mad_obj.total_time() / 1000))


###############################################################################
# Global Functions
###############################################################################
def build_tracks(top_dir):
  """
  used to build track objects from the base directory MP3s
  """
  track_list = []
  for root, dirs, files in os.walk(top_dir, topdown=True):
    for name in files:
      if re.search(".mp3", name):
        track_list.append(Track(os.path.join(root, name), name))
  return track_list

def build_cd(cd):
  print "\n\033[1;32m=============================="
  print "BUILDING CD"
  print "CD Num: %s" %(cd.number)
  print "CD Key: %s" %(cd.key)
  print "Total length (s): %s" %(cd.length)
  print "=================================="
  print "\033[0m"
  print "\033[1;31mEncoding MP3s to WAVs... this might take awhile...\033[0m"
  create_wavs(cd)
  print "\033[1;31mMerging WAVs... this might take awhile as well...\033[0m"
  merge_wavs(cd)

def create_wavs(cd):
  count = 1
  for track in cd.track_lst:
    if count < 10:
      wav = "0%s.wav" %(count)
    else:
      wav = "%s.wav" %(count)
    track.wav = os.path.join(cd.wav_dir, wav)
    proc = subprocess.Popen(["/usr/bin/lame", "--decode", "%s" %track.path,\
      "%s" %track.wav], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print "ENCODING: '%s' AS '%s'\n" %(track.path, track.wav)
    stderr,stdout = proc.communicate()
    if len(stderr) > 0:
      print "\n******** ERROR **********\n\n"
      print stdout,stderr
      print "\n*************************\n\n"
    count += 1

def merge_wavs(cd):
  cd.wav_path = os.path.join(cd.wav_dir, "%s_%s" %(cd.number, cd.key))
  cd.cue_path = cd.wav_path
  wavs = []

  for track in cd.track_lst:
    wavs.append("%s" %os.path.join(track.wav))
  cmd_cue = ["/usr/bin/shntool", "cue"]
  cmd_wav = ["/usr/bin/shntool", "join", "-n", "-O", "always", "-a", cd.wav_path]
  cd.wav_path += ".wav"
  for wv in wavs:
    cmd_cue.append(wv)
    cmd_wav.append(wv)

  proc_cue = subprocess.Popen(cmd_cue, stdout=subprocess.PIPE)
  print "\n\033[1;34mBuidling Cue file . . .\n\033[0m"
  cue_out = proc_cue.communicate()
  build_cue_file(cd, cue_out[0])
  print "\n\033[1;34mBuidling Single WAV file . . .\n\033[0m"
  proc_wav = subprocess.Popen(cmd_wav)
  proc_wav.communicate()

def build_cue_file(cd, cue_out):
  cd.cue_path = os.path.join(cd.wav_dir, "%s_%s.cue" %(cd.number, cd.key))
  print cd.cue_path
  for line in re.split("\n", cue_out):
    if re.search("\s*INDEX:", line):
      cd.track_idx.append(line)
  cue = cd.build_cue()
  cue_file = open(cd.cue_path, "w")
  cue_file.write(cue)
  cue_file.close()

###############################################################################
# Main
###############################################################################

def main():

  max_cd_length = (80*60)
  root_folder = parse_args()
  key_lst = []
  cd_lst = []
  cd_tk_lst = []
  cd_length = 0
  count = 1
  track_total = 1
  track_lst = sorted(build_tracks(root_folder), key=lambda track: track.key)
  last_track = None

  #group tracks by key onto cds
  for track in track_lst:
      if last_track and (last_track.key != track.key or cd_length + track.length > max_cd_length):
        cd_lst.append(CD(root_folder, cd_tk_lst, last_track.key, cd_length, count))
        last_track = None
        cd_length = 0
        cd_tk_lst = []
        count += 1
      else:
        cd_length += track.length
        last_track = track
      cd_tk_lst.append(track)
      track_total += 1
  
  for cd in cd_lst:
    if len(cd.track_lst) > 0:
      build_cd(cd)
  print "\nTotal # Tracks in this genre: %s\n" %track_total

def parse_args():
  try:
    opts, args = getopt.getopt(sys.argv[1:], "p:h", ["help","path="])
  except getopt.GetoptError, err:
    usage()
    sys.exit(2)
  output = None
  verbose = False
  for o, a in opts:
    if o in ("-p", "--path"):
      path = a 
    elif o in ("-h", "--help"):
      usage()
      sys.exit()
    else:
      print "\n**** unknown option: %s ****\n" %o
      print usage()
      sys.exit(1)
  return path

def usage():
  usage = \
  """ 
    ./key2cue.py -p <path> | --path=<path>

    Purpose is to burn a collection of MP3s that have been put through 

    path  - The root folder which contains the MP3s you would like to burn. For
            now make sure these are seperated by genre and run build CD list for
            each genre.

            I.E. /mp3s/* would read recursivly through all directories and burn
                         your MP3s according to track Key.
  """

# Leave at bottom of python to init main.
if __name__ == "__main__":
  main()

